package Rangel.common.world.helper;

import adf.agent.info.AgentInfo;
import adf.agent.info.ScenarioInfo;
import adf.agent.info.WorldInfo;
import Rangel.common.world.tools.PolygonUtil;
import Rangel.common.world.tools.Util;
import Rangel.common.world.entity.RangelBlockade;
import Rangel.common.world.entity.RangelBuilding;
import Rangel.common.world.entity.RangelRoad;
import rescuecore2.misc.Pair;
import rescuecore2.misc.geometry.Point2D;
import rescuecore2.standard.entities.*;
import rescuecore2.worldmodel.EntityID;

import java.awt.*;
import java.awt.geom.Line2D;
import java.util.*;
import java.util.List;

public class VisibilityHelper implements IHelper {

    private RangelWorldHelper worldHelper;
    protected ScenarioInfo scenarioInfo;
    protected AgentInfo agentInfo;
    protected WorldInfo worldInfo;

    public VisibilityHelper(RangelWorldHelper worldHelper, ScenarioInfo scenarioInfo, AgentInfo agentInfo, WorldInfo worldInfo) {
        this.worldHelper = worldHelper;
        this.scenarioInfo = scenarioInfo;
        this.agentInfo = agentInfo;
        this.worldInfo = worldInfo;
        //视线距离
    }


    @Override
    public void init() {
        setBuildingsVisitablePart();
    }//设置建筑物可访问部分

    @Override
    public void update() {
        if (worldHelper.getSelfHuman() == null) {
            return;
        }
        RangelBuilding RangelBuilding;
        //如果人在建筑物中，距离在视觉距离以内，建筑就被搜寻了
        if (worldHelper.getSelfPosition() instanceof Building) {
            RangelBuilding = worldHelper.getRangelBuilding(worldHelper.getSelfPosition().getID());
            int distance = Util.distance(worldInfo.getLocation(agentInfo.getID()), worldInfo.getLocation(RangelBuilding.getSelfBuilding().getID()));
            if (distance < scenarioInfo.getPerceptionLosMaxDistance()) {//距离小于获得感觉的最大损失距离
                worldHelper.setBuildingVisited(RangelBuilding.getID(), true);

                //以下说明用于移除过去看见在此位置的市民
                //但是现在他们被从这里救助走了
                Set<Civilian> toRemoves = new HashSet<Civilian>();
                for (Civilian civ : RangelBuilding.getCivilians()) {
                    if (!worldHelper.getCiviliansSeen().contains(civ)) {
                        toRemoves.add(civ);
                    }
                }
                RangelBuilding.getCivilians().removeAll(toRemoves);
            }
        }

        for (Building building : worldHelper.getBuildingsSeen()) {
            RangelBuilding = worldHelper.getRangelBuilding(building.getID());
            if (RangelBuilding.isVisited()) {
                worldHelper.updateEmptyBuildingState(RangelBuilding, true);
                continue;
            }
            if (canISeeInside(RangelBuilding)) {//能否看见里面
                worldHelper.setBuildingVisited(building.getID(), true);
            }
            updateVisibility(RangelBuilding);//更新可见性
        }
    }

    private void updateVisibility(RangelBuilding RangelBuilding) {//更新可见    todo
        RangelRoad RangelRoad = null;
        List<Polygon> polygons;
        RangelBuilding.getCenterVisitRoadPoints().clear();
        Set<EntityID> roadSet = RangelBuilding.getCenterVisitRoadShapes().keySet();
        if (roadSet.isEmpty()) {
            RangelBuilding.setVisitable(RangelBuilding.isReachable());
        } else {
            Set<Point> toAdd = new HashSet<>();
            boolean visitable = false;
            for (EntityID road : roadSet) {
                polygons = RangelBuilding.getCenterVisitRoadShapes().get(road);
                RangelRoad = worldHelper.getRangelRoad(road);
                if (!RangelRoad.isReachable()) {
                    continue;
                }
                for (Polygon polygon : polygons) {
                    Point point = Util.getPointInPolygon(polygon);//获取多边形中的点
                    if (RangelRoad.notSeen() || RangelRoad.getRangelBlockades().isEmpty()) {
                        visitable = true;
                        RangelBuilding.addCenterVisitRoadPoints(RangelRoad, point);
                    } else {
                        for (RangelBlockade RangelBlockade : RangelRoad.getRangelBlockades()) {
                            if (point != null) {
                                if (RangelBlockade.getPolygon().contains(point)) {
                                    toAdd.remove(point);
                                    break;
                                } else {
                                    visitable = true;
                                    toAdd.add(point);
                                }
                            }
                        }
                    }
                }
            }
            for (Point point : toAdd) {
                RangelBuilding.addCenterVisitRoadPoints(RangelRoad, point);
            }
            RangelBuilding.setVisitable(visitable);
        }
    }

    public void createCenterVisitShapes(RangelBuilding RangelBuilding) {
        int[] xs = new int[6];
        int[] ys = new int[6];
        for (Edge edge : RangelBuilding.getSelfBuilding().getEdges()) {
            if (edge.isPassable()) {//是否可通过
                Pair<Point2D, Point2D> twoPoints = RangelBuilding.getEdgeVisibleCenterPoints().get(edge);
                Point2D c1 = new Point2D(twoPoints.first().getX(), twoPoints.first().getY());
                Point2D c2 = new Point2D(twoPoints.second().getX(), twoPoints.second().getY());
                Point2D e1, e2, a1, a2;
                rescuecore2.misc.geometry.Line2D l1 = new rescuecore2.misc.geometry.Line2D(c1, edge.getStart());
                rescuecore2.misc.geometry.Line2D l2 = new rescuecore2.misc.geometry.Line2D(c2, edge.getEnd());
                if (!Util.intersects(l1, l2)) {//不相交
                    e1 = edge.getStart();
                    e2 = edge.getEnd();
                } else {
                    e1 = edge.getEnd();
                    e2 = edge.getStart();
                }
                int viewRange = scenarioInfo.getPerceptionLosMaxDistance();
                a1 = Util.getPointInDistance(new Line2D.Double(c1.getX(), c1.getY(), e1.getX(), e1.getY()), c1, viewRange);
                a2 = Util.getPointInDistance(new Line2D.Double(c2.getX(), c2.getY(), e2.getX(), e2.getY()), c2, viewRange);
                xs[0] = (int) c1.getX();
                xs[1] = (int) e1.getX();
                xs[2] = (int) a1.getX();
                xs[3] = (int) a2.getX();
                xs[4] = (int) e2.getX();
                xs[5] = (int) c2.getX();
                ys[0] = (int) c1.getY();
                ys[1] = (int) e1.getY();
                ys[2] = (int) a1.getY();
                ys[3] = (int) a2.getY();
                ys[4] = (int) e2.getY();
                ys[5] = (int) c2.getY();
                Polygon shape = new Polygon(xs, ys, 6);
                setRoadPartOfPolygon(shape, RangelBuilding);
                RangelBuilding.addCenterVisitShapes(shape);
            }
        }
    }

    private Set<StandardEntity> getVisibleEntities(RangelBuilding RangelBuilding) {//获取可见实体
        Set<StandardEntity> visibleEntities = new HashSet<>();
        Area area1, area2;
        for (EntityID neighbourID : RangelBuilding.getSelfBuilding().getNeighbours()) {
            area1 = (Area) worldInfo.getEntity(neighbourID);
            visibleEntities.add(area1);
            for (EntityID id : area1.getNeighbours()) {
                area2 = (Area) worldInfo.getEntity(id);
                visibleEntities.add(area2);
            }
        }
        return visibleEntities;
    }

    private void setRoadPartOfPolygon(Polygon shapePolygon, RangelBuilding RangelBuilding) {//设置多边形的道路部分
        Set<StandardEntity> visibleEntities = new HashSet<>(getVisibleEntities(RangelBuilding));
        for (StandardEntity entity : visibleEntities) {
            Polygon polygon;
            if (entity instanceof Road) {
                RangelRoad RangelRoad = worldHelper.getRangelRoad(entity.getID());
                polygon = PolygonUtil.retainPolygon(shapePolygon, RangelRoad.getPolygon());
                if (polygon.npoints > 2) {
                    RangelBuilding.addCenterVisitRoadShapes(RangelRoad, polygon);
                }
            }
        }
    }

    public void setBuildingsVisitablePart() {
        for (RangelBuilding RangelBuilding : worldHelper.getRangelBuildings()) {
            createCenterVisitShapes(RangelBuilding);//创建中心访问形状
            updateVisibility(RangelBuilding);
        }
    }

    public boolean canISeeInside(RangelBuilding RangelBuilding) {

        ArrayList<Building> buildingSeen = new ArrayList<>(worldHelper.getBuildingsSeen());
        if (!buildingSeen.contains(RangelBuilding.getSelfBuilding())) {
            return false;
        }
        boolean canSee = false;
        boolean inBuilding = false;
        Pair<Integer, Integer> locationPair = worldInfo.getLocation(agentInfo.getID());
        rescuecore2.misc.geometry.Point2D locationPoint = new rescuecore2.misc.geometry.Point2D(locationPair.first(), locationPair.second());
        StandardEntity position = worldInfo.getPosition(agentInfo.getID());
        if (position instanceof Building) {
            if (position.equals(RangelBuilding.getSelfBuilding())) {
                return true;
            }
            inBuilding = true;
        }
        FOR1:
        for (Polygon polygon : RangelBuilding.getCenterVisitShapes()) {
            if (polygon.contains(locationPair.first(), locationPair.second())) {
                canSee = true;
                for (Edge edge : RangelBuilding.getSelfBuilding().getEdges()) {
                    if (edge.isPassable()) {
                        Pair<Point2D, Point2D> centerPoints = RangelBuilding.getCenterPointsFrom(edge);
                        FOR2:
                        for (Building building : buildingSeen) {
                            for (Edge edge1 : building.getEdges()) {
                                if (!edge1.isPassable()) {
                                    if (Util.intersects(edge1.getLine(), new rescuecore2.misc.geometry.Line2D(locationPoint, centerPoints.first())) ||
                                            Util.intersects(edge1.getLine(), new rescuecore2.misc.geometry.Line2D(locationPoint, centerPoints.second()))) {
                                        canSee = false;
                                        if (!inBuilding) {
                                            RangelBuilding.getCenterVisitRoadShapes().remove(agentInfo.getPosition());
                                        }
                                        break FOR2;
                                    }
                                }
                            }
                        }
                        if (canSee) {
                            break FOR1;
                        }

                    }
                }
            }
        }
        return canSee;
    }
}
